鏘鏘鏘鏘鏘——鏘!
且說昨日,蘇某與蘇同學二人,對著一冊指指點點。
談的是名籍,論的是屬,最後甚至請出了一隻會說話的小黃鴨。
如今籍庫之制已定,權責既明,兵馬已齊。
也該是時候 —— 撰寫術文!
庫表既成,權責已定,此時不書,更待何時!
好好好,說得好!
此時不書,更待何時,先來建 repo。


一段好的旅程就應該先有個好的名字
就決定是你了!e-fdhs.v2!
聽起來像隨便亂取的
隨便啦,看得懂就好。
先來看看後端要選什麼?應該還是 FastAPI 吧。
我也這麼覺得,非同步架構給我的負擔應該不會太大。
ok 加油。
先幫你把 conda 搞定。
接下來交給你了。

現在的 AI 已經比你那時好用多了,加油!
先裝個 FastAPI,
寫個基本架構再說。

(下 prompt [規劃模式])
撰寫database.py
利用db.dbml建立model.py
並利用model.py建立service.py

這邊問了要用 SQLAlchemy 2 async 還是要用 sync。
那這邊既然都用了 FastAPI 了,那就用 async 吧。
歐?那你知道如果用 sync 會發生什麼事嗎?
大概就是……同步查詢會卡住?
async 比較快?
等等,先不要急著說 async 比較快。
這兩件事不是同一回事。
總之先讓模型繼續工作吧。

它又問說 ORM 模型完成後,資料表結構由什麼方式建立/更新?
這兩個有差嗎?
嗯,好問題,我知道差在哪裡,但是細節我不太熟,我問一下 GPT 好了。
ok 好,這兩種方式處理的是不太一樣的問題。
假設今天我們在 SQLAlchemy 裡寫好了這張表:
class Account(Base):
__tablename__ = "accounts"
id = mapped_column(UUID, primary_key=True)
account = mapped_column(String)
如果使用 create_all(),SQLAlchemy 會去資料庫看:
「喔?accounts 不存在?」
那就幫你建一張。
那不是很好嗎?
如果你打算從此以後再也不改 Schema 的話,確實很好。
怎麼可能。
對,所以問題來了。
假設過了一個禮拜,我突然覺得:
不行,我需要知道這個帳號叫什麼名字。
於是 Model 變成:
class Account(Base):
__tablename__ = "accounts"
id = mapped_column(UUID, primary_key=True)
account = mapped_column(String)
display_name = mapped_column(String, nullable=True)
然後再跑一次 create_all()。
你猜會怎樣?
什麼都不會做?
y。
它看到 accounts 已經存在,就不會自動把現有的表改成你現在 Model 的樣子。
……那我自己下
ALTER TABLE?
可以。
然後下次再改一次 Schema,你再寫一次。
再下次,再寫一次。
等哪天 Production 的資料庫跟你本機長得不一樣,你就可以開始考古:
「這張表到底經歷過什麼?」
這就是 Alembic 要處理的問題。
它不是單純看著現在的 Model,然後把資料庫「啪!」一下變成一模一樣。
我們會留下每一次 Schema 變更的 Migration:
001_create_accounts
↓
002_add_display_name
↓
003_add_group_id
每一次 Schema 的變更,都會留下一份 Migration。
喔,聽起來有點像 Git?
可以這麼說,資料庫 Schema 專用的 Git。
就決定是你了!Alembic。

它又問說建立或更新帳號時,service.py 如何處理密碼?
這邊的話我幫你選。
選僅接受 password_hash,
這樣你之後要自己寫驗證層會比較方便。

Agent 問被帳號或子群組引用的群組/職位/角色,要如何刪除?
這題你自己選。
嗯……
第二個應該先排除吧。
今天如果我把「資訊股長」這個職位刪掉,總不能順便把全校資訊股長的帳號一起揚了。
聽起來挺刺激的 hhh。
軟刪除好像可以,但我們現在真的需要嗎?
我是覺得應該還不用。
那就第一個?
還有人用,就不給刪。
而且你還記得昨天 DBML 裡寫了什麼嗎?
Foreign Key?
嗯哼,既然一個帳號還指向某個職位,那這個職位就不能憑空消失。
不然你的 position_id 的 FK 要指去哪?
總之,先選 拒絕刪除。
真的想刪,就先把引用它的帳號處理乾淨。

Agent 又詢問查詢帳號有效權限時,要採用哪個合併規則?
它問題真多。
選一,先合併後再套 deny。
為什麼不選第二個「例外完全覆蓋」?
那會變成只要出現 Override,原本 Position 和 Role 的權限就全部不算。
但我們昨天設計 Override 的目的,是要修正例外,不是整包權限砍掉重算。

我看看,沒問題了,執行吧。

e-fdhs\backend
├── alembic
│ ├── versions
│ │ └── 20260920_0001_initial_authorization_schema.py
│ ├── env.py
│ └── script.py.mako
├── database
│ ├── __init__.py
│ ├── database.py
│ ├── db.dbml
│ ├── model.py
│ └── service.py
├── pytest-cache-files-b7rs_707
├── tests
│ ├── conftest.py
│ └── test_authorization_services.py
├── .env
├── .gitignore
├── alembic.ini
├── example.env
├── main.py
└── requirements.txt
嗯,看起來沒什麼大問題。
今天應該可以收工了。
你就打算這樣收工了?
差不多吧。
差不多?
你是不是還欠我一個問題?
嗯,好像有那麼一回事,啥問題來著?
剛剛你問我如果用 sync 會怎樣。
我說「async 比較快」,然後你叫我先不要急著下結論。
所以 async 到底快在哪?
喔。
對齁。
那我出個功課吧。
回去想想看。
time.sleep(5)
跟
await asyncio.sleep(5)
這兩段程式,有什麼不一樣?
不就一個同步、一個非同步?
對啊。
但它們不是都要等五秒嗎?
既然都是五秒——
那 async 到底快在哪裡?
明天我們再來看看。